﻿// <copyright file="MachineVirtualRegistryEntryBlock.cs" company="Sinclair Community College">
// Copyright 2010 Sinclair Community College
// </copyright>
// <originalAuthor>Patrick S. Seymour (patrick.seymour@gmail.com)</originalAuthor>

namespace SinclairCC.AppV
{
    using System;
    using System.Diagnostics.CodeAnalysis;

    /// <summary>
    /// This class represents a registry entry in the machine-only registry section
    /// of a CP file (section 0x09).
    /// </summary>
    internal class MachineVirtualRegistryEntry
    {
        /// <summary>
        /// The relative byte offset to the name of the entry inside string/value block.
        /// </summary>
        private int nameOffset;

        /// <summary>
        /// The size of the name in bytes.
        /// </summary>
        private int nameSize;

        /// <summary>
        /// The relative byte offset to the value data inside string/value block.
        /// </summary>
        /// <remarks>
        /// This field is only used with registry values and keys having default value;
        /// otherwise, the field is initialized to 0xFFFFFFFF on key entries.
        /// </remarks>
        private int valueDataOffset;

        /// <summary>
        /// The size of the value data in bytes.
        /// </summary>
        /// <remarks>
        /// This field is only used with registry values and keys having default value;
        /// otherwise, the field is initialized to 0x00000000 on key entries.
        /// </remarks>
        private int valueDataSize;

        /// <summary>
        /// This field is reserved and is initialized to all zeroes.
        /// </summary>
        [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "This field is marked as reserved in the App-V File Format Specification.")]
        private byte[] reserved1;

        /// <summary>
        /// The last written timestamp for the registry entry.
        /// </summary>
        /// <remarks>
        /// This field is stored as a Win32 FILETIME structure.
        /// The format is the UTC (GMT) time zone.
        /// </remarks>
        private DateTime lastWrittenTimestamp;

        /// <summary>
        /// This field is reserved and is initialized to all zeroes.
        /// </summary>
        [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "This field is marked as reserved in the App-V File Format Specification.")]
        private int reserved2;

        /// <summary>
        /// Indicates attributes for the registry entry.
        /// </summary>
        private MachineRegistryEntryAttributes attributes;

        /// <summary>
        /// The type of the registry entry.
        /// </summary>
        private RegistryEntryType entryType;

        /// <summary>
        /// The type of the registry value.
        /// </summary>
        /// <remarks>
        /// This field must be observed in order to decode the value data successfully.
        /// A Registry key entry without a default value has this field initialized to all zeroes.
        /// </remarks>
        private int valueType;

        /// <summary>
        /// This field is set to 1 if the entry for this value in Section 1’s .Attributes field
        /// has the HasValue flag (0x00000400) set.
        /// </summary>
        private int hasValues;

        /// <summary>
        /// This field is reserved and is initialized to all zeroes.
        /// </summary>
        [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "This field is marked as reserved in the App-V File Format Specification.")]
        private int reserved3;

        /// <summary>
        /// The pointer to the parent entry of the key in the form of an index number.
        /// </summary>
        /// <remarks>
        /// After reading in all the entries, this pointer points to the correct entry’s
        /// index number. If there is not a parent entry (the root element) for this key,
        /// this field is initialized to 0xFFFFFFFF.
        /// </remarks>
        private int pointerToParent;

        /// <summary>
        /// The pointer to the first value entry of the key in the form of an index number.
        /// </summary>
        /// <remarks>
        /// After reading in all the entries, this pointer points to the correct entry’s
        /// index number. If there are no values beneath this key — or the entry being parsed
        /// is a value entry — this field is initialized to 0xFFFFFFFF.
        /// </remarks>
        private int pointerToValue;

        /// <summary>
        /// The pointer to the first child key entry (or subkey) of the key in the form
        /// of an index number.
        /// </summary>
        /// <remarks>
        /// After reading in all the entries, this pointer points to the correct entry’s
        /// index number. If there are no keys beneath this key — or the entry being parsed
        /// is a value entry — this field is initialized to 0xFFFFFFFF.
        /// </remarks>
        private int pointerToChildKey;

        /// <summary>
        /// The pointer to the next key entry in the same parent key in the form of an index number.
        /// </summary>
        /// <remarks>
        /// This information can be used to traverse all keys inside a given key. After reading in
        /// all the entries, this pointer points to the correct entry’s index number. If there are
        /// no more additional keys under the same parent key — or the entry being parsed is a value
        /// entry — this field is initialized to 0xFFFFFFFF.
        /// </remarks>
        private int pointerToNextKey;

        /// <summary>
        /// Initializes a new instance of the MachineVirtualRegistryEntry class.
        /// </summary>
        /// <param name="dataStream">
        /// A memory stream that contains the raw data for this block.
        /// </param>
        public MachineVirtualRegistryEntry(System.IO.MemoryStream dataStream)
        {
            this.ReadSection(dataStream);
        }

        /// <summary>
        /// Gets the relative byte offset to the name of the entry inside string/value block.
        /// </summary>
        public int NameOffset
        {
            get { return this.nameOffset; }
        }

        /// <summary>
        /// Gets the size of the name in bytes.
        /// </summary>
        public int NameSize
        {
            get { return this.nameSize; }
        }

        /// <summary>
        /// Gets the relative byte offset to the value data inside string/value block.
        /// </summary>
        /// <remarks>
        /// This field is only used with registry values and keys having default value;
        /// otherwise, the field is initialized to 0xFFFFFFFF on key entries.
        /// </remarks>
        public int ValueDataOffset
        {
            get { return this.valueDataOffset; }
        }

        /// <summary>
        /// Gets the size of the value data in bytes.
        /// </summary>
        /// <remarks>
        /// This field is only used with registry values and keys having default value;
        /// otherwise, the field is initialized to 0x00000000 on key entries.
        /// </remarks>
        public int ValueDataSize
        {
            get { return this.valueDataSize; }
        }

        /// <summary>
        /// Gets the last written timestamp for the registry entry.
        /// </summary>
        public DateTime LastWritten
        {
            get { return this.lastWrittenTimestamp; }
        }

        /// <summary>
        /// Gets the attributes of the registry entry.
        /// </summary>
        public MachineRegistryEntryAttributes Attributes
        {
            get { return this.attributes; }
        }

        /// <summary>
        /// Gets the type of the registry entry.
        /// </summary>
        public RegistryEntryType EntryType
        {
            get { return this.entryType; }
        }

        /// <summary>
        /// Gets the data type of the data stored in the registry entry.
        /// </summary>
        public Microsoft.Win32.RegistryValueKind ValueType
        {
            get
            {
                if (System.Enum.IsDefined(typeof(Microsoft.Win32.RegistryValueKind), this.valueType))
                {
                    return (Microsoft.Win32.RegistryValueKind)this.valueType;
                }
                else
                {
                    return Microsoft.Win32.RegistryValueKind.Unknown;
                }
            }
        }

        /// <summary>
        /// Gets a value indicating whether the entry has a data value
        /// associated with it.
        /// </summary>
        public bool HasValues
        {
            get { return this.hasValues == 1; }
        }

        /// <summary>
        /// Gets a pointer to the parent entry of the key in the form of an index number.
        /// </summary>
        /// <remarks>
        /// After reading in all the entries, this pointer points to the correct entry’s
        /// index number. If there is not a parent entry (the root element) for this key,
        /// this field is initialized to 0xFFFFFFFF.
        /// </remarks>
        public int PointerToParent
        {
            get { return this.pointerToParent; }
        }

        /// <summary>
        /// Gets a pointer to the first value entry of the key in the form of an index number.
        /// </summary>
        /// <remarks>
        /// After reading in all the entries, this pointer points to the correct entry’s
        /// index number. If there are no values beneath this key — or the entry being parsed
        /// is a value entry — this field is initialized to 0xFFFFFFFF.
        /// </remarks>
        public int PointerToValue
        {
            get { return this.pointerToValue; }
        }

        /// <summary>
        /// Gets a pointer to the first child key entry (or subkey) of the key in the form
        /// of an index number.
        /// </summary>
        /// <remarks>
        /// After reading in all the entries, this pointer points to the correct entry’s
        /// index number. If there are no keys beneath this key — or the entry being parsed
        /// is a value entry — this field is initialized to 0xFFFFFFFF.
        /// </remarks>
        public int PointerToChildKey
        {
            get { return this.pointerToChildKey; }
        }

        /// <summary>
        /// Gets a pointer to the next key entry in the same parent key in the form of an index number.
        /// </summary>
        /// <remarks>
        /// This information can be used to traverse all keys inside a given key. After reading in
        /// all the entries, this pointer points to the correct entry’s index number. If there are
        /// no more additional keys under the same parent key — or the entry being parsed is a value
        /// entry — this field is initialized to 0xFFFFFFFF.
        /// </remarks>
        public int PointerToNextKey
        {
            get { return this.pointerToNextKey; }
        }

        /// <summary>
        /// Reads the actual data for this section from a memory stream.
        /// </summary>
        /// <param name="dataStream">
        /// A memory stream containing the raw data for this registry entry block.
        /// </param>
        private void ReadSection(System.IO.MemoryStream dataStream)
        {
            System.IO.BinaryReader streamReader = new System.IO.BinaryReader(dataStream);

            this.nameOffset = streamReader.ReadInt32();
            this.nameSize = streamReader.ReadInt32();
            this.valueDataOffset = streamReader.ReadInt32();
            this.valueDataSize = streamReader.ReadInt32();
            this.reserved1 = streamReader.ReadBytes(8);
            this.lastWrittenTimestamp = DateTime.FromFileTimeUtc(streamReader.ReadInt64());
            this.reserved2 = streamReader.ReadInt32();
            this.attributes = (MachineRegistryEntryAttributes)streamReader.ReadInt32();
            this.entryType = (RegistryEntryType)streamReader.ReadInt32();
            this.valueType = streamReader.ReadInt32();
            this.hasValues = streamReader.ReadInt32();
            this.reserved3 = streamReader.ReadInt32();
            this.pointerToParent = streamReader.ReadInt32();
            this.pointerToValue = streamReader.ReadInt32();
            this.pointerToChildKey = streamReader.ReadInt32();
            this.pointerToNextKey = streamReader.ReadInt32();
        }
    }
}
